import { Gizmo } from './gizmo/index';
import { Camera } from './camera';
import { Border } from './border';
import { Scripts } from '../util/scripts';
import { Msg } from '../msg';
import { Emitter } from '@/ui-kit/index';
import { IAssetInfo } from '../store/types';
import { scenesStore } from '../store/scenes';
import { cloneDeep, debounce } from 'lodash';
import { instanceGameObject } from './inquirer/crate-object/data';
import { getNodeBehaviors, getNodeProperty, getNodeVariables } from './dump';
import { PropertyState } from '../property/store';
import assetsStore from '../store/assets';
import { toRaw } from 'vue';
// CanvasNode(MainScene.ts)
//   |___ Camera(Scene)
//   |___ gameNode
//   |___ gizmoNode
export class SceneAssistant {
    private camera: Camera | null = null;
    private gizmo: Gizmo | null = null;
    private border: Border | null = null;
    
    private gameNode: cc.Node | null = null;
    public selectNode: cc.Node | null = null;
    public scale = 1;
    
    private nodeCanvas: cc.Node | null = null;
    
    constructor() {
    }
    
    private sceneID: string | null = null;
    public asset: IAssetInfo | null = null;
    
    private _initScene() {
        const scene: cc.Scene = new cc.Scene('Scene');
        const nodeCanvas = new cc.Node('canvas');
        this.nodeCanvas = nodeCanvas;
        const canvas: cc.Canvas = nodeCanvas.addComponent(cc.Canvas);
        scene.addChild(nodeCanvas);
        cc.director.runSceneImmediate(scene);
        Emitter.on(Msg.RenderScene, async (asset: IAssetInfo) => {
            await this.saveSceneImmediately();
            const sceneID = asset.data;
            this.asset = asset;
            if (sceneID && this.sceneID !== sceneID) {
                this.sceneID = sceneID;
                const data = scenesStore().getSceneData(sceneID);
                if (data) {
                    await this.createScene(toRaw(data));
                }
            }
        });
    }
    
    onResize(width?: number, height?: number) {
        this.camera?.update();
    }
    
    public init() {
        this._initScene();
        this.camera = new Camera();
        const cameraNode = this.camera.init((event: cc.Event.EventTouch) => {
            const position = event.getLocation();
            this.onSceneClick(position);
        });
        this.nodeCanvas?.addChild(cameraNode, 0);
        
        this.gameNode = new cc.Node('gameNode');
        this.nodeCanvas?.addChild(this.gameNode, 1);
        
        this.gizmo = new Gizmo();
        const gizmoNode = this.gizmo.init();
        this.nodeCanvas?.addChild(gizmoNode, 2);
        
        this.border = new Border();
        const borderNode = this.border.init();
        this.nodeCanvas?.addChild(borderNode, 3);
    }
    
    onScale(scale: number) {
        if (scale <= 0.3) scale = 0.3;
        if (scale >= 2) scale = 2;
        this.scale = cc.Camera.main.zoomRatio = scale;
        this.border?.update();
        this.gizmo?.update();
        this.camera?.update();
    }
    
    onMove(deltaX = 1, deltaY = 1) {
        cc.Camera.main.node.x -= deltaX / this.scale;
        cc.Camera.main.node.y += deltaY / this.scale;
    }
    
    onWheel(delta: number) {
        this.scale += delta * 0.0005;
        this.onScale(this.scale);
    }
    
    private _hitTest(pos: cc.Vec2, node: cc.Node) {
        const rect: cc.Rect = node.getBoundingBoxToWorld();
        const wordPosition = cc.v2();
        cc.Camera.main.getScreenToWorldPoint(pos, wordPosition);
        return rect.contains(wordPosition);
    }
    
    public onSceneClick(position: cc.Vec2) {
        // 先判断已经选中的节点是否能命中
        if (this.selectNode && this.selectNode.isValid) {
            if (this._hitTest(position, this.selectNode)) {
                return;
            }
        }
        
        let selectNode = null;
        const rootNode = this.gameNode;
        if (rootNode) {
            for (let i = 0; i < rootNode.children.length; i++) {
                const child: cc.Node = rootNode.children[i];
                if (this._hitTest(position, child)) {
                    selectNode = child;
                    break;
                }
            }
        }
        this.select(selectNode);
    }
    
    addGameNode(node: cc.Node, save = true) {
        if (node && node.isValid) {
            this.gameNode?.addChild(node);
            if (save) {
                this.saveSceneImmediately();
            }
        }
    }
    
    public saveScene = debounce(async () => {
        this.saveSceneImmediately();
    }, 300);
    
    async saveSceneImmediately() {
        if (this.gameNode && this.sceneID) {
            const ret = await sail.serializeScene(this.gameNode);
            scenesStore().saveScene(this.sceneID, ret);
            return true;
        }
        return false;
    }
    
    modifyVariable(id: string, value: any) {
        const script = this.getSailGameObjectScript();
        if (script) {
            script.modifyVariable(id, value);
        }
    }
    
    async removeBehavior(objectID: string, bundle: string) {
        assetsStore().removeObjectBehavior(objectID, bundle);
        const children = this.gameNode?.children;
        if (children) {
            for (let i = 0; i < children.length; i++) {
                const child = children[i];
                const script: any = child.getComponent(Scripts.SailGameObject);
                if (script && script.originObjectID === objectID) {
                    // @ts-ignore
                    const ret = await script.removeBehavior(bundle);
                    if (!ret) {
                        console.error(`remove behavior(${bundle}) failed!`);
                    }
                }
            }
        }
    }
    
    addBehavior(objectID: string, bundle: string) {
        assetsStore().addObjectBehavior(objectID, bundle);
        this.gameNode?.children.forEach(child => {
            const script = child.getComponent(Scripts.SailGameObject);
            if (script && script.originObjectID === objectID) {
                script.addBehavior(bundle);
            }
        });
    }
    
    async createScene(data: sail.SailGameObject[]) {
        if (Array.isArray(data)) {
            this.resetScene();
            for (let i = 0; i < data.length; i++) {
                let item = data[i];
                const node: cc.Node | null = await instanceGameObject(item.originObjectID,
                    item,
                    {
                        save: false,
                        select: false,
                    },
                );
            }
            this.select(null);
        }
    }
    
    resetScene() {
        this.select(null);
        this.gameNode?.destroyAllChildren();
    }
    
    setProperty(bundle: string, key: string, val: any) {
        const script = this.getSailGameObjectScript();
        if (script) {
            script.setProperty(bundle, key, val);
        }
    }
    
    getSailGameObjectScript() {
        if (this.selectNodeValid()) {
            return this.selectNode?.getComponent(Scripts.SailGameObject) || null;
        }
        return null;
    }
    
    private sizeChange() {
        this.gizmo?.update();
    }
    
    select(node: cc.Node | null) {
        Emitter.emit(Msg.ShowNodeProperty, node);
        if (node !== this.selectNode) {
            if (this.selectNode) {
                this.selectNode.off(cc.Node.EventType.SIZE_CHANGED, this.sizeChange, this);
            }
            this.selectNode = node;
            if (this.selectNode) {
                this.selectNode.on(cc.Node.EventType.SIZE_CHANGED, this.sizeChange, this);
            }
            this.gizmo?.update();
        }
    }
    
    deleteSelectNode() {
        if (this.selectNode && this.selectNode.isValid) {
            this.selectNode.destroy();
            this.select(null);
        }
    }
    
    selectNodeValid(): boolean {
        return !!(this.selectNode && this.selectNode.isValid);
    }
    
    // private _dumpProp(attr:Record<string, any>) {
    //     const ret = {};
    //     if (attr.hasOwnProperty('visible')) {
    //         ret['visible'] = attr['visible'];
    //     }
    //     return ret;
    // }
    
    
    async dumpSelectNode(): Promise<PropertyState | null> {
        const node = this.selectNode;
        if (node) {
            const script: any = node.getComponent(Scripts.SailGameObject);
            if (script) {
                const allProps = await getNodeProperty(script);
                const behaviors = getNodeBehaviors(script);
                const variables = getNodeVariables(script);
                const { originObjectID, bundle } = script;
                
                return cloneDeep({
                    id: '',
                    originObjectID,
                    bundle,
                    props: allProps,
                    variables,
                    behaviors,
                });
            }
        }
        return null;
    }
    
    
}


export const sceneAssistant = new SceneAssistant();